/**
 * Handles native metadata
 */

package jpsearch;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;

import org.apache.sanselan.ImageReadException;
import org.apache.sanselan.common.BinaryFileFunctions;

public class JPSearchSegment extends BinaryFileFunctions implements JPSearchConstants {

	private byte [] rawSegment;
	private byte [] rawNativeMetadata;
	private byte [] rawXMLMetadata;

	private int version;
	private int noElementaryData;

	private int lengthOfSEMBlock;
	private String schemaIdentifier;

	private int lengthAnnotation;
	private int confidenceMeasure;
	private String creationDateTime;
	private String lastUpdateDateTime;
	private int sizeofAuthor;
	private String author;
	private int flagRead;

	private int lengthData;
	private int encoding;
	private String data;

	//Basic Constructor, creates a basic metadata segment (used for images not containing any JPSearch metadata)
	public JPSearchSegment(){
		noElementaryData = 1; //metadata.size();
		version = 1;

		schemaIdentifier = new String("JPSearch:schema:coremetadata");

		confidenceMeasure = 55;

		SimpleDateFormat simpleDateFormat  = new SimpleDateFormat("yyyy/MM/dd-HH:mm:ss");
		String date = simpleDateFormat.format(new Date());
		creationDateTime = date;
		lastUpdateDateTime = date;

		author = new String("Author");
		sizeofAuthor = author.length();		
		flagRead = 0;
		encoding = 84;

		data = new String("");
		lengthData = data.length();

		updateSegment();
	}

	//JPSearch constructor that fills field values given the metadata and XMLdata
	public JPSearchSegment(List metadata, String XMLdata) throws UnsupportedEncodingException{ //later change to List<List> for including SEM blocks
		int i = 0;

		noElementaryData = 1; //metadata.size();
		version = ((Integer)metadata.get(i++)).intValue();	
		Object o = metadata.get(i++);
		if(o instanceof byte[])
			schemaIdentifier = new String(((byte [])o), 0, ((byte [])o).length, "utf-8");
		else
			schemaIdentifier = new String((String)o);

		confidenceMeasure = ((Integer)metadata.get(i++)).intValue();
		creationDateTime = (String)metadata.get(i++);
		lastUpdateDateTime = (String)metadata.get(i++);
		author = (String)metadata.get(i++);
		sizeofAuthor = author.length();		
		flagRead = ((Integer)metadata.get(i++)).intValue();
		encoding = ((Integer)metadata.get(i++)).intValue();

		data = new String(XMLdata);
		lengthData = data.length();

		updateSegment();
	}

	//converts an integer to a 4-bytes array
	public static byte[] int2FourBytes(int myInteger){
		return ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).putInt(myInteger).array();
	}

	//Constructor that fills fields value data given a byte array with the data
	public JPSearchSegment(byte [] segmentData){

		InputStream is = new ByteArrayInputStream(segmentData);
		int ind = 0;

		try {
			if (segmentData.length < JPSEARCH_IDENTIFIER.length)
				throw new ImageReadException("Invalid JPSearch Segment.");

			//Skip the JPSearch Identifier 'JPS\0'
			for (int i = 0; i < JPSEARCH_IDENTIFIER.length; i++, ind++){
				int c = readByte("JPSearch Identifier", is, "Not a Valid JPSearch Identifier");
				if (c < JPSEARCH_IDENTIFIER[i])
					throw new ImageReadException("Invalid JPSearch Segment.");
			}		

			// Segment Data of JPSearch file

			//Version
			version = readByte("JPSearch Version", is, "Not a Valid JPSearch Version"); /// <--- USE THIS
			ind++;

			//Number of ElemetaryMetadata
			noElementaryData = read2Bytes("Number of Elementary Metadata", is, "Not a Valid Number of Elementary Metadata", 'M');
			ind += 2;

			//ELEMENTARY METADATA
			//StartCode 'SEM\0'
			for (int i = 0; i < JPSEARCH_SEM_IDENTIFIER.length; i++, ind++){
				int c = readByte("JPSearch Identifier", is, "Not a Valid JPSearch Identifier");
				if (c < JPSEARCH_SEM_IDENTIFIER[i])
					throw new ImageReadException("Invalid JPSearch SEM Segment.");
			}	

			//Get SEM length
			lengthOfSEMBlock = read4Bytes("Length of Elementary Metadata", is, "Not a Valid Length of Elementary Metadata", 'M');
			ind += 4;

			//Schema Identifier (JPSearch:schema:coremetadata)
			byte [] sch = readByteArray("Schema", JPSEARCH_SCHEMA.length, is);			
			schemaIdentifier = new String(sch, 0, JPSEARCH_SCHEMA.length, "utf-8");			
			String xmlID = new String( JPSEARCH_SCHEMA, 0, JPSEARCH_SCHEMA.length, "utf-8");

			if(!schemaIdentifier.equals(xmlID))
				throw new ImageReadException("Invalid JPSearch Schema.");

			ind += JPSEARCH_SCHEMA.length;

			//ANNOTATION
			//Length of annotation
			lengthAnnotation = read4Bytes("Length of Annotation", is, "Not a Valid Length of Annotation", 'M');
			ind += 4;

			//Confidence Measure
			confidenceMeasure = readByte("Confidence Measure", is, "Not a Valid Confidence Measure"); /// <--- USE THIS
			ind++;

			//Creation DateTime
			byte [] cDT = readByteArray("Creation DateTime", 20, is);
			creationDateTime = new String(cDT, 0, cDT.length, "utf-8");
			//Date d = DateFormat.parse(creationDT);
			ind += 20;

			//LastUpdate DateTime
			byte [] uDT = readByteArray("Last Update DateTime", 20, is);
			lastUpdateDateTime = new String(uDT, 0, uDT.length, "utf-8");	
			//Date d = DateFormat.parse(creationDT);
			ind += 20;

			//Size of Author field
			sizeofAuthor = readByte("Size of Author Field", is, "Not a Valid Size of Author Field");
			ind++;

			//Author
			byte [] auth = readByteArray("Last Update DateTime", sizeofAuthor + 1, is);
			author = new String(auth, 0, auth.length, "utf-8");	
			ind = ind + sizeofAuthor + 1;

			//FlagReadOnly | 00:Allowed, FF:Not allowed
			flagRead = readByte("FlagReadOnly", is, "Not a Valid FlagReadOnly");
			ind++;

			//Length of DATA
			lengthData = read4Bytes("Length of DATA", is, "Not a Valid Length of DATA", 'M');
			ind += 4;

			//Encoding | 54: XML, 42: Schema supplier Method, 0: No method, Others: Future uses
			encoding = readByte("Encoding", is, "Not a Valid Encoding");
			ind++;

			//DATA (usually XML)
			byte [] XMLdata = readByteArray("Data", is.available(), is);
			data = new String(XMLdata, 0, XMLdata.length, "utf-8");

			rawSegment = segmentData.clone();
			rawNativeMetadata = copyBytes(segmentData, 0, ind);
			rawXMLMetadata = XMLdata.clone();

		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ImageReadException e) {
			e.printStackTrace();
		}
	}

	//After altering the fields of the Segment it is necessary to
	//do an update to recreate the raw segments
	public void updateSegment(){

		lengthAnnotation = 4 + 1 + 20 + 20 + 1 + 1 + sizeofAuthor;
		lengthOfSEMBlock = 4 + 4 + schemaIdentifier.length() + lengthAnnotation + 4 + 1 + lengthData;

		//prepare Segment Data bytes
		ByteArrayOutputStream os = new ByteArrayOutputStream();
		try {
			os.write(JPSEARCH_IDENTIFIER); //write 'JPS\0'
			byte [] verBytes = convertShortToByteArray(version, 'M'); //1 byte
			os.write(verBytes,1,1); //write version

			byte [] EDBytes = convertShortToByteArray(noElementaryData, 'M'); //2 bytes
			os.write(EDBytes); //write number of elementary data

			os.write(JPSEARCH_SEM_IDENTIFIER); //write 'SEM\0'

			byte [] LSEMBlockBytes = int2FourBytes(lengthOfSEMBlock); //4 bytes
			os.write(LSEMBlockBytes); //write length of SEM block

			os.write(schemaIdentifier.getBytes());  //write schema
			os.write(0);  //write null value at the end of schema

			byte [] lAnnotationBytes = int2FourBytes(lengthAnnotation);//4 bytes
			os.write(lAnnotationBytes);

			byte [] cmBytes = convertShortToByteArray(confidenceMeasure, 'M'); //1 byte
			os.write(cmBytes,1,1); //write confidence measure

			//byte [] date = creationDateTime.getBytes();
			os.write(creationDateTime.getBytes());  //write creation date
			os.write(0);  //write null value at the end of the date
			os.write(lastUpdateDateTime.getBytes()); //write last update date
			os.write(0);  //write null value at the end of the date

			byte [] sAuthBytes = convertShortToByteArray(sizeofAuthor, 'M'); //1 byte
			os.write(sAuthBytes,1,1);  //write size of author
			os.write(author.getBytes()); //write author
			os.write(0);  //write null value at the end of the author

			byte [] flagBytes = convertShortToByteArray(flagRead, 'M'); //1 byte
			os.write(flagBytes,1,1); //write read only flag

			byte [] lData = int2FourBytes(lengthData);//4 bytes
			os.write(lData); //write length of data

			byte [] enc = convertShortToByteArray(encoding, 'M'); //1 byte
			os.write(enc,1,1); //write encoding

			rawNativeMetadata = os.toByteArray();

			os.write(data.getBytes()); //write XML data

			rawSegment = os.toByteArray();

			rawXMLMetadata = data.getBytes();

		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	//Getters
	public byte[] getRawNativeMetadata(){
		return rawNativeMetadata;
	}

	public byte[] getRawXMLMetadata() {
		return rawXMLMetadata;
	}

	public byte[] getRawSegment(){
		return rawSegment;
	}

	public int getVersion() {
		return version;
	}

	public int getNoElementaryData() {
		return noElementaryData;
	}

	public int getLengthOfSEMBlock() {
		return lengthOfSEMBlock;
	}

	public String getSchemaIdentifier() {
		return schemaIdentifier;
	}

	public int getLengthAnnotation() {
		return lengthAnnotation;
	}

	public int getConfidenceMeasure() {
		return confidenceMeasure;
	}

	public String getCreationDateTime() {
		return creationDateTime;
	}

	public String getLastUpdateDateTime() {
		return lastUpdateDateTime;
	}

	public int getSizeofAuthor() {
		return sizeofAuthor;
	}

	public String getAuthor() {
		return author;
	}

	public int getFlagRead() {
		return flagRead;
	}

	public int getLengthData() {
		return lengthData;
	}

	public int getEncoding() {
		return encoding;
	}

	public String getData() {
		return data;
	}

	//SETTERS
	public void setRawSegment(byte[] rawSegment) {
		this.rawSegment = rawSegment;
	}

	public void setRawNativeMetadata(byte[] rawNativeMetadata) {
		this.rawNativeMetadata = rawNativeMetadata;
	}

	public void setRawXMLMetadata(byte[] rawXMLMetadata) {
		this.rawXMLMetadata = rawXMLMetadata;
	}

	public void setVersion(int version) {
		this.version = version;
		updateSegment();
	}

	public void setSchemaIdentifier(String schemaIdentifier) {
		this.schemaIdentifier = schemaIdentifier;
		updateSegment();
	}

	public void setConfidenceMeasure(int confidenceMeasure) {
		this.confidenceMeasure = confidenceMeasure;
		updateSegment();
	}

	public void setCreationDateTime(String creationDateTime) {
		this.creationDateTime = creationDateTime;
		updateSegment();
	}

	public void setLastUpdateDateTime(String lastUpdateDateTime) {
		this.lastUpdateDateTime = lastUpdateDateTime;
		updateSegment();
	}

	public void setAuthor(String author) {
		this.author = author;
		sizeofAuthor = author.length();
		updateSegment();
	}

	public void setFlagRead(int flagRead) {
		this.flagRead = flagRead;
		updateSegment();
	}

	public void setEncoding(int encoding) {
		this.encoding = encoding;
		updateSegment();
	}

	public void setData(String data) {
		this.data = data;
		lengthData = data.length();
		updateSegment();
	}

	//Checks whether segmentData is a SEM block
	public boolean isSEMSegment(byte segmentData[])
	{
		int index = 0;

		if (segmentData.length < JPSEARCH_SEM_IDENTIFIER.length) //change to JPSearch identifier
			return false;
		for (; index < JPSEARCH_SEM_IDENTIFIER.length; index++)
			if (segmentData[index] < JPSEARCH_SEM_IDENTIFIER[index])
				return false;

		return true;
	}

	//Homemade copyBytes (same as clone)
	public static byte[] copyBytes(byte [] source, int ind, int length){
		byte [] result = new byte[length];
		for(int i = 0; i<length; i++, ind++){
			result[i] = source[ind];
		}
		return result;
	}

	//Prints fields values to the output
	public void dumbJPSearchSegment(){
		System.out.println("Version: " + version);
		System.out.println("Number of elementary data: " + noElementaryData);

		System.out.println("Length of SEM block: " + lengthOfSEMBlock);
		System.out.println("Schema identifier: " + schemaIdentifier);

		System.out.println("Length of Annotation " + lengthAnnotation);
		System.out.println("Confidence Measure: " + confidenceMeasure);
		System.out.println("Creation Date Time: " + creationDateTime);
		System.out.println("Last Update Date Time: " + lastUpdateDateTime);
		System.out.println("Size of Author: " + sizeofAuthor);
		System.out.println("Author: " + author);
		System.out.println("Flag Read Only: " + flagRead);

		System.out.println("Length of Data: " + lengthData);
		System.out.println("Encoding: " + encoding);
		System.out.println("XML Data: " + data);
	}
}
